Türkçe

JavaScript Yineleyici Protokolü'nü anlamak ve uygulamak için kapsamlı bir kılavuz; gelişmiş veri işleme için özel yineleyiciler oluşturmanızı sağlar.

JavaScript Yineleyici Protokolü ve Özel Yineleyicilerin Gizemini Çözmek

JavaScript'in Yineleyici Protokolü, veri yapılarında gezinmek için standart bir yol sağlar. Bu protokolü anlamak, geliştiricilere diziler ve string'ler gibi dahili yinelenebilirlerle verimli bir şekilde çalışma ve belirli veri yapılarına ve uygulama gereksinimlerine göre uyarlanmış kendi özel yinelenebilirlerini oluşturma gücü verir. Bu kılavuz, Yineleyici Protokolü'nün ve özel yineleyicilerin nasıl uygulanacağının kapsamlı bir incelemesini sunar.

Yineleyici Protokolü Nedir?

Yineleyici Protokolü, bir nesnenin nasıl yinelenebileceğini, yani elemanlarına sırayla nasıl erişilebileceğini tanımlar. İki bölümden oluşur: Yinelenebilir (Iterable) protokolü ve Yineleyici (Iterator) protokolü.

Yinelenebilir (Iterable) Protokolü

Bir nesne, Symbol.iterator anahtarına sahip bir metodu varsa Yinelenebilir (Iterable) kabul edilir. Bu metod, Yineleyici (Iterator) protokolüne uyan bir nesne döndürmelidir.

Özünde, yinelenebilir bir nesne kendisi için bir yineleyici oluşturmayı bilir.

Yineleyici (Iterator) Protokolü

Yineleyici (Iterator) protokolü, bir diziden değerlerin nasıl alınacağını tanımlar. Bir nesne, iki özelliğe sahip bir nesne döndüren bir next() metoduna sahipse yineleyici olarak kabul edilir:

next() metodu, Yineleyici protokolünün iş yükünü çeken kısmıdır. next()'e yapılan her çağrı, yineleyiciyi ilerletir ve dizideki bir sonraki değeri döndürür. Tüm değerler döndürüldüğünde, next(), done özelliği true olarak ayarlanmış bir nesne döndürür.

Dahili Yinelenebilirler

JavaScript, doğası gereği yinelenebilir olan birkaç dahili veri yapısı sunar. Bunlar şunları içerir:

Bu yinelenebilirler, for...of döngüsü, spread sözdizimi (...) ve Yineleyici Protokolü'ne dayanan diğer yapılarla doğrudan kullanılabilir.

Dizilerle Örnek:


const myArray = ["elma", "muz", "kiraz"];

for (const item of myArray) {
  console.log(item); // Çıktı: elma, muz, kiraz
}

String'lerle Örnek:


const myString = "Merhaba";

for (const char of myString) {
  console.log(char); // Çıktı: M, e, r, h, a, b, a
}

for...of Döngüsü

for...of döngüsü, yinelenebilir nesneler üzerinde yineleme yapmak için güçlü bir yapıdır. Yineleyici Protokolü'nün karmaşıklıklarını otomatik olarak yönetir, bu da bir dizideki değerlere erişimi kolaylaştırır.

for...of döngüsünün sözdizimi şöyledir:


for (const element of iterable) {
  // Her bir eleman için çalıştırılacak kod
}

for...of döngüsü, yinelenebilir nesneden yineleyiciyi alır (Symbol.iterator kullanarak) ve done true olana kadar yineleyicinin next() metodunu tekrar tekrar çağırır. Her yinelemede, element değişkenine next() tarafından döndürülen value özelliği atanır.

Özel Yineleyiciler Oluşturma

JavaScript dahili yinelenebilirler sağlasa da, Yineleyici Protokolü'nün gerçek gücü, kendi veri yapılarınız için özel yineleyiciler tanımlama yeteneğinde yatar. Bu, verilerinizin nasıl gezileceğini ve erişileceğini kontrol etmenizi sağlar.

İşte özel bir yineleyici nasıl oluşturulur:

  1. Özel veri yapınızı temsil eden bir sınıf veya nesne tanımlayın.
  2. Sınıfınızda veya nesnenizde Symbol.iterator metodunu uygulayın. Bu metod bir yineleyici nesnesi döndürmelidir.
  3. Yineleyici nesnesi, value ve done özelliklerine sahip bir nesne döndüren bir next() metoduna sahip olmalıdır.

Örnek: Basit bir Aralık için Yineleyici Oluşturma

Bir sayı aralığını temsil eden Range adında bir sınıf oluşturalım. Aralıktaki sayılar üzerinde yineleme yapmaya izin vermek için Yineleyici Protokolü'nü uygulayacağız.


class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  [Symbol.iterator]() {
    let currentValue = this.start;
    const that = this; // Yineleyici nesnesi içinde kullanmak için 'this'i yakala

    return {
      next() {
        if (currentValue <= that.end) {
          return {
            value: currentValue++,
            done: false,
          };
        } else {
          return {
            value: undefined,
            done: true,
          };
        }
      },
    };
  }
}

const myRange = new Range(1, 5);

for (const number of myRange) {
  console.log(number); // Çıktı: 1, 2, 3, 4, 5
}

Açıklama:

Örnek: Bir Bağlı Liste için Yineleyici Oluşturma

Başka bir örnek düşünelim: bir bağlı liste veri yapısı için bir yineleyici oluşturma. Bir bağlı liste, her bir düğümün bir değer ve listedeki bir sonraki düğüme bir referans (işaretçi) içerdiği bir düğüm dizisidir. Listenin son düğümü null'a (veya undefined'a) bir referansa sahiptir.


class LinkedListNode {
    constructor(value, next = null) {
        this.value = value;
        this.next = next;
    }
}

class LinkedList {
    constructor() {
        this.head = null;
    }

    append(value) {
        const newNode = new LinkedListNode(value);
        if (!this.head) {
            this.head = newNode;
            return;
        }

        let current = this.head;
        while (current.next) {
            current = current.next;
        }
        current.next = newNode;
    }

    [Symbol.iterator]() {
        let current = this.head;

        return {
            next() {
                if (current) {
                    const value = current.value;
                    current = current.next;
                    return {
                        value: value,
                        done: false
                    };
                } else {
                    return {
                        value: undefined,
                        done: true
                    };
                }
            }
        };
    }
}

// Örnek Kullanım:
const myList = new LinkedList();
myList.append("Londra");
myList.append("Paris");
myList.append("Tokyo");

for (const city of myList) {
    console.log(city); // Çıktı: Londra, Paris, Tokyo
}

Açıklama:

Üreteç (Generator) Fonksiyonları

Üreteç fonksiyonları, yineleyiciler oluşturmak için daha kısa ve zarif bir yol sağlar. Talep üzerine değerler üretmek için yield anahtar kelimesini kullanırlar.

Bir üreteç fonksiyonu, function* sözdizimi kullanılarak tanımlanır.

Örnek: Bir Üreteç Fonksiyonu Kullanarak Yineleyici Oluşturma

Range yineleyicisini bir üreteç fonksiyonu kullanarak yeniden yazalım:


class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
}

const myRange = new Range(1, 5);

for (const number of myRange) {
  console.log(number); // Çıktı: 1, 2, 3, 4, 5
}

Açıklama:

Üreteç fonksiyonları, next() metodunu ve done bayrağını otomatik olarak yöneterek yineleyici oluşturmayı basitleştirir.

Örnek: Fibonacci Dizisi Üreteci

Üreteç fonksiyonlarını kullanmanın bir başka harika örneği de Fibonacci dizisini oluşturmaktır:


function* fibonacciSequence() {
  let a = 0;
  let b = 1;

  while (true) {
    yield a;
    [a, b] = [b, a + b]; // Eşzamanlı güncelleme için yapı bozma ataması
  }
}

const fibonacci = fibonacciSequence();

for (let i = 0; i < 10; i++) {
  console.log(fibonacci.next().value); // Çıktı: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}

Açıklama:

Yineleyici Protokolünü Kullanmanın Faydaları

İleri Düzey Yineleyici Teknikleri

Yineleyicileri Birleştirme

Birden çok yineleyiciyi tek bir yineleyicide birleştirebilirsiniz. Bu, birden çok kaynaktan gelen verileri birleşik bir şekilde işlemeniz gerektiğinde kullanışlıdır.


function* combineIterators(...iterables) {
  for (const iterable of iterables) {
    for (const item of iterable) {
      yield item;
    }
  }
}

const array1 = [1, 2, 3];
const array2 = ["a", "b", "c"];
const string1 = "XYZ";

const combined = combineIterators(array1, array2, string1);

for (const value of combined) {
  console.log(value); // Çıktı: 1, 2, 3, a, b, c, X, Y, Z
}

Bu örnekte, `combineIterators` fonksiyonu argüman olarak herhangi bir sayıda yinelenebilir alır. Her bir yinelenebilir üzerinde döner ve her bir öğeyi yield eder. Sonuç, tüm girdi yinelenebilirlerinden gelen tüm değerleri üreten tek bir yineleyicidir.

Yineleyicileri Filtreleme ve Dönüştürme

Ayrıca, başka bir yineleyici tarafından üretilen değerleri filtreleyen veya dönüştüren yineleyiciler de oluşturabilirsiniz. Bu, verileri bir boru hattında işlemenize, her değere oluşturuldukça farklı işlemler uygulamanıza olanak tanır.


function* filterIterator(iterable, predicate) {
  for (const item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

function* mapIterator(iterable, transform) {
  for (const item of iterable) {
    yield transform(item);
    }
}

const numbers = [1, 2, 3, 4, 5, 6];

const evenNumbers = filterIterator(numbers, (x) => x % 2 === 0);
const squaredEvenNumbers = mapIterator(evenNumbers, (x) => x * x);

for (const value of squaredEvenNumbers) {
    console.log(value); // Çıktı: 4, 16, 36
}

Burada, `filterIterator` bir yinelenebilir ve bir yüklem fonksiyonu alır. Yalnızca yüklemin `true` döndürdüğü öğeleri yield eder. `mapIterator` bir yinelenebilir ve bir dönüştürme fonksiyonu alır. Her öğeye dönüştürme fonksiyonunu uygulamanın sonucunu yield eder.

Gerçek Dünya Uygulamaları

Yineleyici Protokolü, JavaScript kütüphanelerinde ve çerçevelerinde yaygın olarak kullanılır ve özellikle büyük veri setleri veya asenkron işlemlerle uğraşırken çeşitli gerçek dünya uygulamalarında değerlidir.

En İyi Uygulamalar

Sonuç

JavaScript Yineleyici Protokolü, veri yapılarında gezinmek için güçlü ve esnek bir yol sağlar. Yinelenebilir ve Yineleyici protokollerini anlayarak ve üreteç fonksiyonlarından yararlanarak, özel ihtiyaçlarınıza göre özel yineleyiciler oluşturabilirsiniz. Bu, verilerle verimli bir şekilde çalışmanıza, kod okunabilirliğini artırmanıza ve uygulamalarınızın performansını yükseltmenize olanak tanır. Yineleyicilerde ustalaşmak, JavaScript'in yeteneklerinin daha derin bir şekilde anlaşılmasını sağlar ve daha zarif ve verimli kod yazmanıza olanak tanır.